package labox.innovation.gameserver.taskmanager;

import static labox.innovation.gameserver.taskmanager.TaskTypes.TYPE_GLOBAL_TASK;
import static labox.innovation.gameserver.taskmanager.TaskTypes.TYPE_NONE;
import static labox.innovation.gameserver.taskmanager.TaskTypes.TYPE_SHEDULED;
import static labox.innovation.gameserver.taskmanager.TaskTypes.TYPE_SPECIAL;
import static labox.innovation.gameserver.taskmanager.TaskTypes.TYPE_STARTUP;
import static labox.innovation.gameserver.taskmanager.TaskTypes.TYPE_TIME;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javolution.util.FastList;
import javolution.util.FastMap;
import labox.innovation.config.Config;
import labox.innovation.db.core.L2DatabaseFactory;
import labox.innovation.gameserver.ThreadPoolManager;
import labox.innovation.gameserver.taskmanager.tasks.TaskCleanUp;
import labox.innovation.gameserver.taskmanager.tasks.TaskRestart;
import labox.innovation.gameserver.taskmanager.tasks.TaskShutdown;
import labox.innovation.gameserver.timer.Timer;

/**
 * @author Layane
 * 
 */
public final class TaskManager {
	protected static final Logger _log = LoggerFactory.getLogger(TaskManager.class.getName());

	protected static final String[] SQL_STATEMENTS = { "SELECT id,task,type,last_activation,param1,param2,param3 FROM global_tasks", "UPDATE global_tasks SET last_activation=? WHERE id=?", "SELECT id FROM global_tasks WHERE task=?", "INSERT INTO global_tasks (task,type,last_activation,param1,param2,param3) VALUES(?,?,?,?,?,?)" };

	private final FastMap<Integer, Task> _tasks = new FastMap<Integer, Task>();

	protected final FastList<ExecutedTask> _currentTasks = new FastList<ExecutedTask>();

	public class ExecutedTask implements Runnable {
		int id;

		long lastActivation;

		Task task;

		TaskTypes type;

		String[] params;

		Timer scheduled;

		public ExecutedTask(Task ptask, TaskTypes ptype, ResultSet rset) throws SQLException {
			task = ptask;
			type = ptype;
			id = rset.getInt("id");
			lastActivation = rset.getLong("last_activation");
			params = new String[] { rset.getString("param1"), rset.getString("param2"), rset.getString("param3") };
		}

		public void run() {
			task.onTimeElapsed(this);
			lastActivation = System.currentTimeMillis();

			Connection con = null;

			try {
				con = L2DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con.prepareStatement(SQL_STATEMENTS[1]);
				statement.setLong(1, lastActivation);
				statement.setInt(2, id);
				statement.executeUpdate();
				statement.close();
			} catch (SQLException e) {
				_log.error("cannot updated the Global Task id ", Config.SERVER_ID, e);
			} finally {
				try {
					con.close();
				} catch (Exception e) {
				}
			}

			if (type == TYPE_SHEDULED || type == TYPE_TIME) {
				stopTask();
			}
		}

		@Override
		public boolean equals(Object object) {
			return id == ((ExecutedTask) object).id;
		}

		public Task getTask() {
			return task;
		}

		public TaskTypes getType() {
			return type;
		}

		public int getId() {
			return id;
		}

		public String[] getParams() {
			return params;
		}

		public long getLastActivation() {
			return lastActivation;
		}

		public void stopTask() {
			task.onDestroy();

			if (scheduled != null)
				scheduled.cancel();

			_currentTasks.remove(this);
		}

	}

	public static TaskManager getInstance() {
		return SingletonHolder._instance;
	}

	private TaskManager() {
		initializate();
		startAllTasks();
	}

	private void initializate() {
		registerTask(new TaskCleanUp());
		registerTask(new TaskRestart());
		registerTask(new TaskShutdown());
	}

	public void registerTask(Task task) {
		int key = task.getName().hashCode();
		if (!_tasks.containsKey(key)) {
			_tasks.put(key, task);
			task.initializate();
		}
	}

	private void startAllTasks() {
		Connection con = null;
		try {
			con = L2DatabaseFactory.getInstance().getConnection();
			PreparedStatement statement = con.prepareStatement(SQL_STATEMENTS[0]);
			ResultSet rset = statement.executeQuery();

			while (rset.next()) {
				Task task = _tasks.get(rset.getString("task").trim().toLowerCase().hashCode());

				if (task == null)
					continue;

				TaskTypes type = TaskTypes.valueOf(rset.getString("type"));

				if (type != TYPE_NONE) {
					ExecutedTask current = new ExecutedTask(task, type, rset);
					if (launchTask(current))
						_currentTasks.add(current);
				}

			}

			rset.close();
			statement.close();

		} catch (Exception e) {
			_log.error("error while loading Global Task table ", Config.SERVER_ID, e);
			e.printStackTrace();
		} finally {
			try {
				con.close();
			} catch (Exception e) {
			}
		}
	}

	private boolean launchTask(ExecutedTask task) {
		final ThreadPoolManager scheduler = ThreadPoolManager.getInstance();
		final TaskTypes type = task.getType();

		if (type == TYPE_STARTUP) {
			task.run();
			return false;
		} else if (type == TYPE_SHEDULED) {
			int delay = Integer.valueOf(task.getParams()[0]);
			task.scheduled = new Timer(delay, false, task);
			task.scheduled.start();
			return true;
		} else if (type == TYPE_TIME) {
			try {
				Date desired = DateFormat.getInstance().parse(task.getParams()[0]);
				int diff = (int) (desired.getTime() - System.currentTimeMillis());
				if (diff >= 0) {
					task.scheduled = new Timer(diff, false, task);
					task.scheduled.start();
					return true;
				}
				_log.info("Task " + task.getId() + " is obsoleted.");
			} catch (Exception e) {
			}
		} else if (type == TYPE_SPECIAL) {
			Timer result = task.getTask().launchSpecial(task);
			if (result != null) {
				task.scheduled = result;
				task.scheduled.start();
				return true;
			}
		} else if (type == TYPE_GLOBAL_TASK) {
			long interval = Long.valueOf(task.getParams()[0]) * 86400000L;
			String[] hour = task.getParams()[1].split(":");

			if (hour.length != 3) {
				_log.warn("Task {} has incorrect parameters ", task.getId());
				return false;
			}

			Calendar check = Calendar.getInstance();
			check.setTimeInMillis(task.getLastActivation() + interval);

			Calendar min = Calendar.getInstance();
			try {
				min.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour[0]));
				min.set(Calendar.MINUTE, Integer.parseInt(hour[1]));
				min.set(Calendar.SECOND, Integer.parseInt(hour[2]));
			} catch (Exception e) {
				_log.error("Bad parameter on task :" + task.getId(), Config.SERVER_ID, e);
				return false;
			}

			long delay = min.getTimeInMillis() - System.currentTimeMillis();

			if (check.after(min) || delay < 0) {
				delay += interval;
			}

			task.scheduled = new Timer((int) delay, false, task);
			task.scheduled.start();

			return true;
		}

		return false;
	}

	public static boolean addUniqueTask(String task, TaskTypes type, String param1, String param2, String param3) {
		return addUniqueTask(task, type, param1, param2, param3, 0);
	}

	public static boolean addUniqueTask(String task, TaskTypes type, String param1, String param2, String param3, long lastActivation) {
		Connection con = null;

		try {
			con = L2DatabaseFactory.getInstance().getConnection();
			PreparedStatement statement = con.prepareStatement(SQL_STATEMENTS[2]);
			statement.setString(1, task);
			ResultSet rset = statement.executeQuery();

			if (!rset.next()) {
				statement = con.prepareStatement(SQL_STATEMENTS[3]);
				statement.setString(1, task);
				statement.setString(2, type.toString());
				statement.setLong(3, lastActivation);
				statement.setString(4, param1);
				statement.setString(5, param2);
				statement.setString(6, param3);
				statement.execute();
			}

			rset.close();
			statement.close();

			return true;
		} catch (SQLException e) {
			_log.error("cannot add the unique task ", Config.SERVER_ID, e);
		} finally {
			try {
				con.close();
			} catch (Exception e) {
			}
		}

		return false;
	}

	public static boolean addTask(String task, TaskTypes type, String param1, String param2, String param3) {
		return addTask(task, type, param1, param2, param3, 0);
	}

	public static boolean addTask(String task, TaskTypes type, String param1, String param2, String param3, long lastActivation) {
		Connection con = null;

		try {
			con = L2DatabaseFactory.getInstance().getConnection();
			PreparedStatement statement = con.prepareStatement(SQL_STATEMENTS[3]);
			statement.setString(1, task);
			statement.setString(2, type.toString());
			statement.setLong(3, lastActivation);
			statement.setString(4, param1);
			statement.setString(5, param2);
			statement.setString(6, param3);
			statement.execute();

			statement.close();
			return true;
		} catch (SQLException e) {
			_log.error("cannot add the task: {} ", Config.SERVER_ID, e);
		} finally {
			try {
				con.close();
			} catch (Exception e) {
			}
		}

		return false;
	}

	@SuppressWarnings("synthetic-access")
	private static class SingletonHolder {
		protected static final TaskManager _instance = new TaskManager();
	}
}